// api/public/public.service.js
import * as fs from "fs/promises";
import * as path from "path";
import axios from "axios";
import BaseService from "../../services/base.service.js";

class PublicService extends BaseService {
  getModel() {
    // Since we don't have a model/collection, we return null
    // This is just to satisfy the BaseService abstract method
    return null;
  }

  async getResourcesDir() {
    // Use environment variable for the resources directory
    return process.env.PUBLIC_RESOURCES_DIRECTORY || path.join(process.cwd(), 'public-resources');
  }

  isValidResourcePath(resourcePath) {
    // Prevent directory traversal and other security issues
    // This regex allows alphanumeric, underscores, hyphens, and forward slashes (for subdirectories)
    // but blocks paths with '..' which could be used for traversal
    return /^[a-zA-Z0-9_\-\/]+$/.test(resourcePath) && !resourcePath.includes('..');
  }

  async getAllResourcesInfo() {
    const resourcesDir = await this.getResourcesDir();
    
    // Ensure directory exists
    try {
      await fs.access(resourcesDir);
    } catch (error) {
      // If directory doesn't exist, create it
      if (error.code === 'ENOENT') {
        await fs.mkdir(resourcesDir, { recursive: true });
        return []; // Return empty array for newly created directory
      }
      throw error;
    }
    
    return this.processDirectory(resourcesDir);
  }

  async processDirectory(dirPath, basePath = '') {
    const result = [];
    const entries = await fs.readdir(dirPath, { withFileTypes: true });
    
    for (const entry of entries) {
      const relativePath = path.join(basePath, entry.name);
      const fullPath = path.join(dirPath, entry.name);
      
      if (entry.isDirectory()) {
        // If it's a directory, get its stats and recursively process contents
        const stats = await fs.stat(fullPath);
        const children = await this.processDirectory(fullPath, relativePath);
        
        result.push({
          name: entry.name,
          path: relativePath,
          type: 'directory',
          size: this.calculateDirectorySize(children),
          modifiedTime: stats.mtime,
          createdTime: stats.birthtime,
          children: children
        });
      } else if (entry.isFile()) {
        // If it's a file, get its stats
        const stats = await fs.stat(fullPath);
        
        result.push({
          name: entry.name,
          path: relativePath,
          type: 'file',
          size: stats.size,
          modifiedTime: stats.mtime,
          createdTime: stats.birthtime,
          extension: path.extname(entry.name).toLowerCase()
        });
      }
    }
    
    // Sort: directories first, then files, both alphabetically
    return result.sort((a, b) => {
      if (a.type === 'directory' && b.type === 'file') return -1;
      if (a.type === 'file' && b.type === 'directory') return 1;
      return a.name.localeCompare(b.name);
    });
  }

  calculateDirectorySize(children) {
    return children.reduce((total, item) => {
      return total + (typeof item.size === 'number' ? item.size : 0);
    }, 0);
  }

  async getResourceInfo(resourceName) {
    const resourcesDir = await this.getResourcesDir();
    const resourcePath = path.join(resourcesDir, resourceName);
    
    try {
      const stats = await fs.stat(resourcePath);
      
      if (stats.isDirectory()) {
        // If it's a directory, get detailed information including contents
        const children = await this.processDirectory(resourcePath);
        
        return {
          name: path.basename(resourcePath),
          path: resourceName,
          type: 'directory',
          size: this.calculateDirectorySize(children),
          modifiedTime: stats.mtime,
          createdTime: stats.birthtime,
          children: children
        };
      } else if (stats.isFile()) {
        // If it's a file, get detailed information
        return {
          name: path.basename(resourcePath),
          path: resourceName,
          type: 'file',
          size: stats.size,
          modifiedTime: stats.mtime,
          createdTime: stats.birthtime,
          extension: path.extname(resourcePath).toLowerCase()
        };
      }
    } catch (error) {
      if (error.code === 'ENOENT') {
        // Resource not found
        return null;
      }
      throw error;
    }
    
    return null;
  }

  /**
   * Search GitHub and GitLab repositories using Google Custom Search API
   * @param {string} query - Search query
   * @param {number} page - Page number for pagination
   * @param {number} pageSize - Number of results per page
   * @returns {Object} Search results
   */
  async searchGitRepositories(query, page = 1, pageSize = 10) {
    try {
      // Make sure API key is available
      const apiKey = process.env.GOOGLE_SEARCH_API_KEY;
      const searchEngineId = process.env.GOOGLE_SEARCH_ENGINE_ID;
      
      if (!apiKey || !searchEngineId) {
        throw new Error("Google Search API credentials are not configured properly");
      }

      // Calculate start index for pagination (Google's API uses 1-based indexing)
      const startIndex = ((page - 1) * pageSize) + 1;
      
      // Add site: operator to limit search to GitHub and GitLab
      const formattedQuery = `${query} site:github.com OR site:gitlab.com`;
      
      // Make request to Google Custom Search API
      const response = await axios.get('https://www.googleapis.com/customsearch/v1', {
        params: {
          key: apiKey,
          cx: searchEngineId,
          q: formattedQuery,
          start: startIndex,
          num: pageSize
        }
      });
      
      // Process and format the response
      const results = this.formatSearchResults(response.data);
      
      return {
        query,
        page,
        pageSize,
        totalResults: response.data.searchInformation?.totalResults || 0,
        totalPages: Math.ceil((response.data.searchInformation?.totalResults || 0) / pageSize),
        results
      };
    } catch (error) {
      // Handle API-specific errors
      if (error.response) {
        const status = error.response.status;
        const data = error.response.data;
        
        // Custom error with status code
        const customError = new Error(
          data?.error?.message || "Failed to search repositories"
        );
        customError.statusCode = status;
        
        throw customError;
      }
      
      // Re-throw other errors
      throw error;
    }
  }

  /**
   * Format search results from Google API response
   * @param {Object} apiResponse - Google API response object
   * @returns {Array} Formatted search results
   */
  formatSearchResults(apiResponse) {
    if (!apiResponse.items || !Array.isArray(apiResponse.items)) {
      return [];
    }

    return apiResponse.items.map(item => {
      // Determine if result is from GitHub or GitLab
      const isGitHub = item.link.includes('github.com');
      const isGitLab = item.link.includes('gitlab.com');
      const site = isGitHub ? 'github' : (isGitLab ? 'gitlab' : 'other');
      
      // Extract last updated info if available
      let lastUpdated = null;
      if (item.pagemap?.metatags && item.pagemap.metatags[0]) {
        lastUpdated = item.pagemap.metatags[0]['og:updated_time'] || 
                      item.pagemap.metatags[0]['article:modified_time'];
      }
      
      // Extract repository owner and name if possible
      let repoOwner = null;
      let repoName = null;
      
      if (isGitHub || isGitLab) {
        const urlParts = item.link.split('/');
        // Remove empty parts and protocol
        const cleanParts = urlParts.filter(part => part && !part.includes('http'));
        
        // Format is usually: [domain.com, owner, repo, ...]
        if (cleanParts.length >= 3) {
          repoOwner = cleanParts[1];
          repoName = cleanParts[2];
        }
      }

      return {
        title: item.title,
        link: item.link,
        snippet: item.snippet,
        site,
        lastUpdated: lastUpdated ? new Date(lastUpdated).toISOString() : null,
        formattedLastUpdated: lastUpdated ? this.formatLastUpdated(lastUpdated) : 'Unknown date',
        repoOwner,
        repoName,
        cacheId: item.cacheId
      };
    });
  }

  /**
   * Format the last updated date in a human-readable format
   * @param {string} dateString - ISO date string
   * @returns {string} Formatted date string
   */
  formatLastUpdated(dateString) {
    try {
      const date = new Date(dateString);
      const now = new Date();
      const diffMs = now - date;
      const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
      
      if (diffDays < 1) {
        return 'Updated today';
      } else if (diffDays === 1) {
        return 'Updated yesterday';
      } else if (diffDays < 7) {
        return `Updated ${diffDays} days ago`;
      } else if (diffDays < 30) {
        const weeks = Math.floor(diffDays / 7);
        return `Updated ${weeks} ${weeks === 1 ? 'week' : 'weeks'} ago`;
      } else if (diffDays < 365) {
        const months = Math.floor(diffDays / 30);
        return `Updated ${months} ${months === 1 ? 'month' : 'months'} ago`;
      } else {
        const years = Math.floor(diffDays / 365);
        return `Updated ${years} ${years === 1 ? 'year' : 'years'} ago`;
      }
    } catch (error) {
      return 'Unknown date';
    }
  }
}

export default new PublicService();